#include "Player.h"
#include "Ladder.h"
#include "CheckLadderBelow.h"
#include "Rolling.h"
#include "ZeroAttack.h"
#include "DebugLog.h"

#include <memory>
#include <string>
#include <map>
#include <exception>
#include <iostream>

namespace
{
	// Primary Collision Processing
	void playerLadder(Entity* player, Entity* ladder);
	void checkLadderBelowLadder(Entity* checkLadderBelow, Entity* ladder);
	void playerRolling(Entity* player, Entity* rolling);
	void zeroAttackRolling(Entity* zeroAttack, Entity* rolling);

	// Secondary Collision Processing
	void ladderPlayer(Entity* ladder, Entity* player) { playerLadder(player, ladder); }
	void ladderCheckLadderBelow(Entity* ladder, Entity* checkLadderBelow) { checkLadderBelowLadder(checkLadderBelow, ladder); }
	void rollingPlayer(Entity* rolling, Entity* player) { playerRolling(player, rolling); }
	void rollingZeroAttack(Entity* rolling, Entity* zeroAttack) { zeroAttackRolling(zeroAttack, rolling); }

	typedef void (*HitFunctionPtr)(Entity*, Entity*);
	typedef std::map<std::pair<std::string, std::string>, HitFunctionPtr> HitMap;
	std::pair<std::string, std::string> makeStringPair(const char*, const char*);

	HitMap* initializeCollisionMap();

	HitFunctionPtr lookup(const std::string&, const std::string&);
}

void processCollision(Entity* lhs, Entity* rhs)
{
	HitFunctionPtr phf = lookup(lhs->getType(), rhs->getType());
	if (phf)
		phf(lhs, rhs);
	else
		Debug::log("EntityCollision WARNING: Collision with unknown entity");
}

namespace
{
	std::pair<std::string, std::string> makeStringPair(const char* lhs, const char* rhs)
	{
		return std::pair<std::string, std::string>(lhs, rhs);
	}
}

namespace 
{
	HitMap* initializeCollisionMap()
	{
		HitMap* phm = new HitMap;
		(*phm)[makeStringPair("Player", "Ladder")] = &playerLadder;
		(*phm)[makeStringPair("Ladder", "Player")] = &ladderPlayer;
		(*phm)[makeStringPair("CheckLadderBelow", "Ladder")] = &checkLadderBelowLadder;
		(*phm)[makeStringPair("Ladder", "CheckLadderBelow")] = &ladderCheckLadderBelow;
		(*phm)[makeStringPair("Player", "Rolling")] = &playerRolling;
		(*phm)[makeStringPair("Rolling", "Player")] = &rollingPlayer;
		(*phm)[makeStringPair("ZeroAttack", "Rolling")] = &zeroAttackRolling;
		(*phm)[makeStringPair("Rolling", "ZeroAttack")] = &rollingZeroAttack;
		return phm;
	}
	
}

namespace
{
	HitFunctionPtr lookup(const std::string& lhs, const std::string& rhs)
	{
		static std::unique_ptr<HitMap> collisionMap(initializeCollisionMap());
		HitMap::iterator it = collisionMap->find(std::make_pair(lhs, rhs));
		if (it == collisionMap->end())
			return 0;
		return it->second;
	}
}

namespace
{
	void playerLadder(Entity* player, Entity* ladder)
	{
		Player* player_ = dynamic_cast<Player*>(player);
		if (player_)
		{
			if ((player->flags() & LADDER_COLLISION) != LADDER_COLLISION)
			{
				player_->setFlag(LADDER_COLLISION);
				Ladder* ladder_ = dynamic_cast<Ladder*>(ladder);
				if (ladder_)
					player_->setLadder(ladder_);
				else
					Debug::log("void playerLadder WARNING: Ladder Dynamic Cast failed!");
			}
		}
		else
			Debug::log("void playerLadder WARNING: Player Dynamic Cast failed!");

	}
}

namespace
{
	void checkLadderBelowLadder(Entity* checkLadderBelow, Entity* ladder)
	{
		CheckLadderBelow* clb = dynamic_cast<CheckLadderBelow*>(checkLadderBelow);
		Ladder* l = dynamic_cast<Ladder*>(ladder);
		if (l && clb)
		{
			clb->signalPlayer(l);
		}
	}
}

namespace
{
	void playerRolling(Entity* player, Entity* rolling)
	{
		Player* _player = dynamic_cast<Player*>(player);
		Rolling* _rolling = dynamic_cast<Rolling*>(rolling);
		if (_player && _rolling)
		{
			if (_player->canTakeDamage())
				_player->takeDamage(_rolling->getDamage());
		}
		
	}
}

namespace
{
	void zeroAttackRolling(Entity* za, Entity* r)
	{
		Rolling* rolling = dynamic_cast<Rolling*>(r);
		ZeroAttack* zeroAttack = dynamic_cast<ZeroAttack*>(za);
		if (zeroAttack && rolling)
		{
			if (rolling->canTakeDamage())
				rolling->takeDamage(zeroAttack->getDamage());
		}
	}
}